/*
 *  Copyright 2012 Zdeněk Janeček

 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at

 *    http://www.apache.org/licenses/LICENSE-2.0

 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package kiv.pt;

import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Zajišťuje práci se vstupními daty a grafem.
 * @author Zdeněk Janeček
 */
public final class DataGuru {
    private static final int KROK_FIRMA = 10;
    
    private static double[][] distanceTableMista;
    private static double[][] distanceTableZdroje;

    /**
     * Seznam všech odběrných míst. Obsahuje jejich instance.
     */
    public static List<OdberneMisto> mista;
    
    /**
     * Seznam všech zdrojů energie. Obsahuje jejich instance.
     */
    public static List<ZdrojEnergie> zdroje;

    /**
     * Počet domácností z celkového množství odběrných míst.
     */
    public static int pocetDom;
    
    /**
     * Počet firem z celkového množství odběrných míst.
     */
    public static int pocetFirem;
    
    private DataGuru() {
        // není třeba
    }
    
    /**
     * Vytiskne na standardní výstup matici sousednosti odběrných míst. Nejsou
     * zde přítomny zdroje energie.
     */
    public static void printAdjTableMista() {
        for (int i = 0; i < mista.size(); i++) {
            Integer[] sousedi = mista.get(i).seznamSousedu();
            int existNum = 0;
            int exist = sousedi[existNum];
            for (int j = 0; j < mista.size(); j++) {
                if (j == exist) {
                    System.out.print(" 1");
                    existNum++;
                    if (existNum < sousedi.length) {
                        exist = sousedi[existNum];
                    }
                } else {
                    System.out.print(" 0");
                }
            }
            System.out.println();
        }
    }

    /**
     * Vytiskne tabulku vzdáleností pro zdroje energie a jejich přímo propojená
     * odběrná místa. Pak vytiskne redukovanou matici vzdáleností mezi odběrnými
     * místy. Uložena je pouze její horní trojúhelníková matice bez diagonály.
     */
    public static void printDistanceTable() {
        System.out.print("Tabulka vzdáleností zdrojů\n");
        for (int i = 0; i < distanceTableZdroje.length; i++) {
            for (int j = 0; j < distanceTableZdroje[i].length; j++) {
                System.out.print(" " + distanceTableZdroje[i][j]);
            }
            System.out.println();
        }

        System.out.print("Tabulka vzdáleností Míst\n");
        for (int i = 0; i < distanceTableMista.length; i++) {
            for (int j = 0; j < distanceTableMista[i].length; j++) {
                System.out.print(" " + distanceTableMista[i][j]);
            }
            System.out.println();
        }
    }

    /**
     * Inicializuje tabulky vzdáleností pro celou elektrickou síť. Spustit po
     * každé aktualizaci a hned po načtení dat.
     */
    public static void calculateDistances() {
        distanceTableZdroje = new double[zdroje.size()][mista.size()];
        distanceTableMista = new double[mista.size() - 1][];

        // Matice míst bude ukládána jen horní trojúhelníková.
        int size = mista.size() - 1;
        for (int i = 0; i < distanceTableMista.length; i++) {
            distanceTableMista[i] = new double[size];
            for (int j = 0; j < distanceTableMista[i].length; j++) {
                distanceTableMista[i][j] = Double.MAX_VALUE;
            }
            size--;
        }

        for (int i = 0; i < distanceTableZdroje.length; i++) {
            for (int j = 0; j < distanceTableZdroje[i].length; j++) {
                distanceTableZdroje[i][j] = Double.MAX_VALUE;
            }

            Integer[] sousedi = zdroje.get(i).seznamSousedu();
            for (int j = 0; j < sousedi.length; j++) {
                Point2D.Double p1 = zdroje.get(i).getCoordinates();
                Point2D.Double p2 = mista.get(sousedi[j]).getCoordinates();
                distanceTableZdroje[i][sousedi[j]] = distanceBetween(p1, p2);
            }
        }

        for (int i = 0; i < mista.size(); i++) {
            Integer[] sousedi = mista.get(i).seznamSousedu();
            for (int j = 0; j < sousedi.length; j++) {
                int soused = sousedi[j];
                if (i < soused) {
                    // System.out.format("i=%d soused=%d zapisuji na (%d,%d)\n",
                    // i, soused, i, soused - -i - 1);
                    Point2D.Double p1 = mista.get(i).getCoordinates();
                    Point2D.Double p2 = mista.get(soused).getCoordinates();
                    distanceTableMista[i][soused - i - 1] = distanceBetween(p1,
                            p2);
                }
            }
        }
    }

    /**
     * Vrátí vzdálenost mezi body. Nepoužívat a zeptat se na vzdálenost z
     * tabulky.
     * 
     * @param p1
     *            Počáteční bod
     * @param p2
     *            Cílový bod
     * @return Odmocnina ze skalárního součinu vektoru samého se sebou mezi
     *         body.
     */
    public static double distanceBetween(Point2D.Double p1, Point2D.Double p2) {
        double dx = p2.x - p1.x;
        double dy = p2.y - p1.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

    /**
     * Vrátí vzdálenost mezi body pro rychlé porovnání. Používat pro řazení
     * podle vzdálenosti. Mnohem rychlejší než {@code
     * distanceBetween(Point2D.Double p1, Point2D.Double p2)}.
     * 
     * @param x1 Počáteční bod
     * @param y1 Počáteční bod
     * @param x2 Cílový bod
     * @param y2 Cílový bod
     * @return Skalární součin sám se sebou vektoru mezi body.
     */
    public static double distanceBetweenFast(double x1, double y1, double x2,
            double y2) {
        double dx = x2 - x1;
        double dy = y2 - y1;
        return dx * dx + dy * dy;
    }

    /**
     * 
     * @param fromType
     *            true v případě elektrárny, false v případě domácnost
     * @param i
     *            Index místa/elektrárny od které chceme vzdálenost.
     * @param j
     *            Cíl ke kterému potřebujeme znát vzdálenost.
     * @return -1 v případě, kdy nebyla inicializována tabulka vzdáleností
     *         (zavolejte {@code calculateDistances()}), nebo použijte jednu z
     *         metod {@code distanceBetween()} a {@code distanceBetweenFast()}.
     */
    public static double distanceFromTable(boolean fromType, int i, int j) {
        if (i < 0 || j < 0) {
            System.out.format("pozor! i=%d j=%d", i, j);
        }
        
        if ((distanceTableMista == null) || (distanceTableZdroje == null)) {
            return -1;
        }
        
        if (fromType) {
            return distanceTableZdroje[i][j];
        } else if (i == j) {
            return 0.0;
        } else if (i < j) {
            return distanceTableMista[i][j - i - 1];
        } else {
            return distanceTableMista[j][i - j - 1];
        }
    }

    /**
     * @param file Absolutní cesta k datovému souboru.
     * @return 0 v případě že vše bylo OK, 2 že došlo k nějaké chybě.
     * @throws FileNotFoundException 
     */
    public static int nactiDataSite(String file) throws FileNotFoundException {
        File f = new File(file);
        BufferedReader reader = new BufferedReader(new FileReader(f));

        try {
            // První jsou odběrná místa
            String l = reader.readLine();

            if (!l.equals("MISTA")) {
                reader.close();
                System.out.print("Soubor není ve správném formátu");

                // chybový stav formát souboru
                return 2;
            }
            
            int countMist = Integer.parseInt(reader.readLine());
            mista = new ArrayList<OdberneMisto>(countMist);

            int prikonDom = 1;
            int n_dom = 0;
            int prikonFir = 510;
            int prikon;
            for (int i = 0; i < countMist; i++) {
                String curLine = reader.readLine();
                String[] pol = curLine.split(";");

                // 0...false...dom
                // 1...true...firma
                boolean typ = pol[0].charAt(0) == '0' ? false : true;
                double x = java.lang.Double.parseDouble(pol[1]);
                double y = java.lang.Double.parseDouble(pol[2]);

                if (typ) {
                    prikon = prikonFir;
                    prikonFir += KROK_FIRMA;
                } else {
                    if (n_dom >= 5) {
                        prikonDom++;
                        n_dom = 0;
                    }
                    prikon = prikonDom;
                    n_dom++;
                }
                
                OdberneMisto m = new OdberneMisto(new Point2D.Double(x, y),
                        pol.length - 3, i, typ, prikon);
                for (int j = 3; j < pol.length; j++) {
                    m.addSoused(Integer.parseInt(pol[j])-11);
                }
                mista.add(m);
                if (typ) {
                    pocetFirem++;
                } else {
                    pocetDom++;
                }
            }

            // Hlavní elektrárna
            l = reader.readLine();
            if (!l.equals("HL")) {
                reader.close();
                System.out.print("Soubor není ve správném formátu");

                // chybový stav formát souboru
                return 2;
            }

            String curLine = reader.readLine();
            String[] pol = curLine.split(";");
            double x = java.lang.Double.parseDouble(pol[0]);
            double y = java.lang.Double.parseDouble(pol[1]);

            ZdrojEnergie hl = new ZdrojEnergie(new Point2D.Double(x, y),
                    pol.length - 2, 0);

            for (int i = 2; i < pol.length; i++) {
                hl.addSoused(Integer.parseInt(pol[i])-11);
            }

            // Další následují alternativní zdroje
            l = reader.readLine();

            if (!l.equals("ALT")) {
                reader.close();
                System.out.print("Soubor není ve správném formátu");

                // chybový stav formát souboru
                return 2;
            }

            int countAlt = Integer.parseInt(reader.readLine());

            // Na začátek dáme naší hlavní elektrárnu
            zdroje = new ArrayList<ZdrojEnergie>(countAlt + 1);
            zdroje.add(hl);

            for (int i = 0; i < countAlt; i++) {
                curLine = reader.readLine();
                pol = curLine.split(";");
                x = java.lang.Double.parseDouble(pol[0]);
                y = java.lang.Double.parseDouble(pol[1]);

                ZdrojEnergie m = new ZdrojEnergie(new Point2D.Double(x, y),
                        pol.length - 2, i+1);
                for (int j = 2; j < pol.length; j++) {
                    m.addSoused(Integer.parseInt(pol[j])-11);
                }
                zdroje.add(m);
            }

            reader.close();
        } catch (IOException e) {
            System.out.println("Něco se pokazilo při čtení.");
            System.exit(-1);
        }
        return 0;
    }
}
